/*
 * Copyright 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.compose.ui.arkui.breakpoint

import androidx.annotation.MainThread
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.arkui.messenger.Messenger

/**
 * Width breakpoint enums based on HarmonyOS breakpoint system
 * - XS: (0, 320) vp
 * - SM: [320, 600) vp
 * - MD: [600, 840) vp
 * - LG: [840, 1440) vp
 * - XL: [1440, +∞) vp
 */
@Immutable
enum class WidthBreakpoint(val value: String) {
    XS("xs"),
    SM("sm"),
    MD("md"),
    LG("lg"),
    XL("xl");

    companion object {
        fun fromString(value: String): WidthBreakpoint =
            WidthBreakpoint.entries.find { it.value == value } ?: SM

        fun fromIndex(index: Int): WidthBreakpoint = when (index) {
            0 -> XS
            1 -> SM
            2 -> MD
            3 -> LG
            4 -> XL
            else -> SM
        }
    }
}

/**
 * Height breakpoint enums based on HarmonyOS breakpoint system
 * - SM: (0, 0.8) aspect ratio
 * - MD: [0.8, 1.2) aspect ratio
 * - LG: [1.2, +∞) aspect ratio
 */
@Immutable
enum class HeightBreakpoint(val value: String) {
    SM("sm"),
    MD("md"),
    LG("lg");

    companion object {
        fun fromString(value: String): HeightBreakpoint =
            HeightBreakpoint.entries.find { it.value == value } ?: LG

        fun fromIndex(index: Int): HeightBreakpoint = when (index) {
            0 -> SM
            1 -> MD
            2 -> LG
            else -> LG
        }
    }
}

/**
 * Configuration for width breakpoints
 * Field names represent the breakpoint that starts at that threshold
 * (aligned with ArkTS layer naming)
 */
data class BreakpointConfig(
    val sm: Float = 320f,   // Threshold to enter SM breakpoint
    val md: Float = 600f,   // Threshold to enter MD breakpoint
    val lg: Float = 840f,   // Threshold to enter LG breakpoint
    val xl: Float = 1440f   // Threshold to enter XL breakpoint
)

/**
 * Configuration for height breakpoints (aspect ratio thresholds)
 * Field names represent the breakpoint that starts at that threshold
 * (aligned with ArkTS layer naming)
 */
data class HeightBreakpointConfig(
    val md: Float = 0.8f,   // Threshold to enter MD breakpoint
    val lg: Float = 1.2f    // Threshold to enter LG breakpoint
)

/**
 * Manager for handling breakpoint state and communication with ArkTS layer.
 *
 * This class manages the current breakpoint state and provides APIs for:
 * - Getting current width/height breakpoints
 * - Subscribing to breakpoint changes
 * - Manually calculating breakpoints from arbitrary values
 * - Customizing breakpoint thresholds
 *
 * Note: Use the factory function or internal constructor to create instances.
 */
@Stable
class BreakpointManager internal constructor(
    private val messenger: Messenger
) {
    // Current breakpoint states (reactive)
    private val _currentWidthBreakpoint = mutableStateOf(WidthBreakpoint.SM)
    private val _currentHeightBreakpoint = mutableStateOf(HeightBreakpoint.LG)

    val currentWidthBreakpoint: State<WidthBreakpoint> = _currentWidthBreakpoint
    val currentHeightBreakpoint: State<HeightBreakpoint> = _currentHeightBreakpoint

    // Customizable breakpoint configurations
    private var widthBreakpointConfig = BreakpointConfig()
    private var heightBreakpointConfig = HeightBreakpointConfig()

    // 保存当前窗口尺寸，用于阈值改变时重新计算
    private var currentWidthDp: Float = 0f
    private var currentHeightDp: Float = 0f

    // Callback list for subscribers
    private val callbacks = mutableListOf<(WidthBreakpoint, HeightBreakpoint) -> Unit>()

    init {
        registerMessageHandlers()
        updateBreakpoint()
    }

    private fun registerMessageHandlers() {
        // Register handler for breakpoint change notifications from ArkTS
        messenger.onReceive("compose.breakpoint:changed") { message ->
            try {
                // Parse JSON manually: {"widthBreakpoint":1,"heightBreakpoint":2,"widthDp":800,"heightDp":600}
                val widthBp = parseJsonInt(message, "widthBreakpoint")
                val heightBp = parseJsonInt(message, "heightBreakpoint")

                if (widthBp != null && heightBp != null) {
                    _currentWidthBreakpoint.value = WidthBreakpoint.fromIndex(widthBp)
                    _currentHeightBreakpoint.value = HeightBreakpoint.fromIndex(heightBp)

                    // 尝试从消息中解析窗口尺寸
                    parseJsonFloat(message, "widthDp")?.let { currentWidthDp = it }
                    parseJsonFloat(message, "heightDp")?.let { currentHeightDp = it }

                    // Notify all subscribers
                    notifyCallbacks()
                }
                ""
            } catch (e: Exception) {
                e.printStackTrace()
                ""
            }
        }
    }

    /**
     * Simple JSON parser for extracting integer values
     * Parses format: {"key":value}
     */
    private fun parseJsonInt(json: String, key: String): Int? {
        return try {
            val pattern = """"$key"\s*:\s*(\d+)""".toRegex()
            val matchResult = pattern.find(json)
            matchResult?.groupValues?.get(1)?.toIntOrNull()
        } catch (e: Exception) {
            null
        }
    }

    /**
     * Simple JSON parser for extracting float values
     * Parses format: {"key":value} or {"key":value.decimal}
     */
    private fun parseJsonFloat(json: String, key: String): Float? {
        return try {
            val pattern = """"$key"\s*:\s*(\d+(?:\.\d+)?)""".toRegex()
            val matchResult = pattern.find(json)
            matchResult?.groupValues?.get(1)?.toFloatOrNull()
        } catch (e: Exception) {
            null
        }
    }

    /**
     * Get current width breakpoint as string (compatible with RN API)
     * @return Current width breakpoint: "xs", "sm", "md", "lg", or "xl"
     */
    @MainThread
    fun getCurrentWidthBreakpoint(): String {
        return _currentWidthBreakpoint.value.value
    }

    /**
     * Get current height breakpoint as string (compatible with RN API)
     * @return Current height breakpoint: "sm", "md", or "lg"
     */
    @MainThread
    fun getCurrentHeightBreakpoint(): String {
        return _currentHeightBreakpoint.value.value
    }

    /**
     * Get current window width in dp
     * @return Current window width in dp, or 0 if not available
     */
    @MainThread
    fun getWindowWidthDp(): Float {
        return currentWidthDp
    }

    /**
     * Get current window height in dp
     * @return Current window height in dp, or 0 if not available
     */
    @MainThread
    fun getWindowHeightDp(): Float {
        return currentHeightDp
    }

    /**
     * Subscribe to breakpoint changes (compatible with RN API)
     * @param callback Function called when breakpoint changes, receives (widthBp, heightBp)
     * @return Unsubscribe function
     */
    @MainThread
    fun subscribeToBreakpoint(
        callback: (widthBreakpoint: String, heightBreakpoint: String) -> Unit
    ): () -> Unit {
        val wrappedCallback: (WidthBreakpoint, HeightBreakpoint) -> Unit = { w, h ->
            callback(w.value, h.value)
        }
        callbacks.add(wrappedCallback)
        return {
            callbacks.remove(wrappedCallback)
        }
    }

    /**
     * Manually update breakpoint from ArkTS (fetch latest values)
     */
    @MainThread
    fun updateBreakpoint() {
        val widthBpStr = messenger.send("compose.breakpoint:getWidthBreakpoint", "")
        val heightBpStr = messenger.send("compose.breakpoint:getHeightBreakpoint", "")

        widthBpStr?.toIntOrNull()?.let { index ->
            _currentWidthBreakpoint.value = WidthBreakpoint.fromIndex(index)
        }

        heightBpStr?.toIntOrNull()?.let { index ->
            _currentHeightBreakpoint.value = HeightBreakpoint.fromIndex(index)
        }

        // 尝试获取并保存当前窗口尺寸
        tryUpdateWindowSize()

        notifyCallbacks()
    }

    /**
     * 尝试从 ArkTS 获取当前窗口尺寸并保存
     */
    private fun tryUpdateWindowSize() {
        try {
            val widthStr = messenger.send("compose.breakpoint:getWindowWidth", "")
            val heightStr = messenger.send("compose.breakpoint:getWindowHeight", "")

            widthStr?.toFloatOrNull()?.let { width ->
                currentWidthDp = width
            }

            heightStr?.toFloatOrNull()?.let { height ->
                currentHeightDp = height
            }
        } catch (e: Exception) {
            // 如果 ArkTS 不支持获取窗口尺寸，忽略错误
        }
    }

    /**
     * 使用保存的窗口尺寸和当前阈值重新计算断点
     */
    private fun recalculateBreakpoints() {
        if (currentWidthDp <= 0 && currentHeightDp <= 0) {
            return
        }

        // 使用新的阈值计算宽度断点
        val newWidthBp = WidthBreakpoint.fromString(calculateBreakpoint(currentWidthDp, false))

        // 计算高度断点（基于高宽比）
        val aspectRatio = if (currentWidthDp > 0) currentHeightDp / currentWidthDp else 1.0f
        val newHeightBp = HeightBreakpoint.fromString(calculateBreakpoint(aspectRatio, true))

        // 更新断点值
        val widthChanged = newWidthBp != _currentWidthBreakpoint.value
        val heightChanged = newHeightBp != _currentHeightBreakpoint.value

        if (widthChanged || heightChanged) {
            _currentWidthBreakpoint.value = newWidthBp
            _currentHeightBreakpoint.value = newHeightBp
            notifyCallbacks()
        }
    }

    /**
     * Manually calculate breakpoint from arbitrary value (compatible with RN API)
     * @param value The value to calculate (width in vp for width breakpoint, aspect ratio for height)
     * @param isHeight Whether to calculate height breakpoint (default: false for width)
     * @return Breakpoint string: "xs"/"sm"/"md"/"lg"/"xl" for width, "sm"/"md"/"lg" for height
     */
    @MainThread
    fun calculateBreakpoint(value: Float, isHeight: Boolean = false): String {
        if (isHeight) {
            // Calculate height breakpoint based on aspect ratio
            return when {
                value < heightBreakpointConfig.md -> "sm"  // Below MD threshold (0.8) = SM
                value < heightBreakpointConfig.lg -> "md"  // Below LG threshold (1.2) = MD
                else -> "lg"                               // >= LG threshold = LG
            }
        } else {
            // Calculate width breakpoint
            return when {
                value < widthBreakpointConfig.sm -> "xs"  // Below SM threshold (320) = XS
                value < widthBreakpointConfig.md -> "sm"  // Below MD threshold (600) = SM
                value < widthBreakpointConfig.lg -> "md"  // Below LG threshold (840) = MD
                value < widthBreakpointConfig.xl -> "lg"  // Below XL threshold (1440) = LG
                else -> "xl"                              // >= XL threshold = XL
            }
        }
    }

    /**
     * Set custom width breakpoint thresholds (compatible with RN API)
     * @param config Map of breakpoint names to threshold values in vp
     * Example: mapOf("sm" to 320f, "md" to 600f, "lg" to 840f, "xl" to 1440f)
     */
    @MainThread
    fun setBreakpoints(config: Map<String, Float>) {
        widthBreakpointConfig = BreakpointConfig(
            sm = config["sm"] ?: widthBreakpointConfig.sm,
            md = config["md"] ?: widthBreakpointConfig.md,
            lg = config["lg"] ?: widthBreakpointConfig.lg,
            xl = config["xl"] ?: widthBreakpointConfig.xl
        )

        // 方案1：发送新阈值给 ArkTS，让它重新计算断点
        val configJson =
            """{"sm":${widthBreakpointConfig.sm},"md":${widthBreakpointConfig.md},"lg":${widthBreakpointConfig.lg},"xl":${widthBreakpointConfig.xl}}"""
        messenger.send("compose.breakpoint:setWidthBreakpoints", configJson)

        // 如果窗口尺寸还未获取，先尝试获取
        if (currentWidthDp <= 0 && currentHeightDp <= 0) {
            tryUpdateWindowSize()
        }

        // 方案2：在 Kotlin 层立即重新计算（确保状态更新）
        if (currentWidthDp > 0 || currentHeightDp > 0) {
            recalculateBreakpoints()
        }
    }

    /**
     * Set custom height breakpoint thresholds (compatible with RN API)
     * @param config Map of breakpoint names to aspect ratio thresholds
     * Example: mapOf("md" to 0.8f, "lg" to 1.2f)
     */
    @MainThread
    fun setHeightBreakpoints(config: Map<String, Float>) {
        heightBreakpointConfig = HeightBreakpointConfig(
            md = config["md"] ?: heightBreakpointConfig.md,
            lg = config["lg"] ?: heightBreakpointConfig.lg
        )

        // 方案1：发送新阈值给 ArkTS，让它重新计算断点
        val configJson = """{"md":${heightBreakpointConfig.md},"lg":${heightBreakpointConfig.lg}}"""
        messenger.send("compose.breakpoint:setHeightBreakpoints", configJson)

        // 如果窗口尺寸还未获取，先尝试获取
        if (currentWidthDp <= 0 && currentHeightDp <= 0) {
            tryUpdateWindowSize()
        }

        // 方案2：在 Kotlin 层立即重新计算（确保状态更新）
        if (currentWidthDp > 0 || currentHeightDp > 0) {
            recalculateBreakpoints()
        }
    }

    /**
     * Clean up resources and clear all callbacks (compatible with RN API)
     */
    @MainThread
    fun destroy() {
        callbacks.clear()
        // Note: Messenger cleanup is handled by parent
    }

    private fun notifyCallbacks() {
        callbacks.toList().forEach { callback ->
            callback(_currentWidthBreakpoint.value, _currentHeightBreakpoint.value)
        }
    }
}
